package com.wang.project.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wang.project.common.ErrorCode;
import com.wang.project.constant.MQConstant;
import com.wang.project.exception.BusinessException;
import com.wang.project.model.dto.integral.IntegralExchangeInterfaceRequest;
import com.wang.project.model.entity.Integral;
import com.wang.project.model.entity.InterfaceInfo;
import com.wang.project.model.entity.User;
import com.wang.project.model.entity.UserInterfaceInfo;
import com.wang.project.model.modle.MQIntegralModel;
import com.wang.project.model.vo.integral.IntegralExchangeInterfaceVO;
import com.wang.project.model.vo.integral.IntegralExchangeRefreshKeyVO;
import com.wang.project.model.vo.integral.IntegralSignInVO;
import com.wang.project.model.vo.integral.IntegralVO;
import com.wang.project.service.IntegralService;
import com.wang.project.mapper.IntegralMapper;
import com.wang.project.service.InterfaceInfoService;
import com.wang.project.service.UserInterfaceInfoService;
import com.wang.project.service.UserService;
import com.wang.project.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author 胡歌的腿毛
 * @description 针对表【integral】的数据库操作Service实现
 * @createDate 2023-09-27 16:06:36
 */
@Service
@Slf4j
public class IntegralServiceImpl extends ServiceImpl<IntegralMapper, Integral> implements IntegralService {

    @Resource
    private UserService userService;

    @Resource
    private InterfaceInfoService interfaceInfoService;

    @Resource
    private UserInterfaceInfoService userInterfaceInfoService;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired(required = false)
    private Redisson redisson;

    @Resource
    private RocketMQTemplate rocketMQTemplate;

    //redis加锁的key前缀
    final static String redisLockPrefix = "openApi:integral:signIn:";

    //兑换接口次数每次消耗1积分
    final static int integralAmountToOne = 1;

    //兑换刷新key服务 消耗100积分
    final static int integralAmountToRefreshKey = 100;

    final static Map<String, String> integralExchangeTypeMap = new HashMap<>();

    static {
        integralExchangeTypeMap.put("interface", "增加接口次数");
        integralExchangeTypeMap.put("refreshKey", "永久停止刷新key服务");
    }


    /**
     * 用户签到
     * 获取当前登录用户 查询当前用户是否有连续签到的情况 设置bitmap，保存redis， 设置积分，保存到积分表中
     * bitmap的key 统一使用 yyyy/dd/mm:userId  2023/09/24:123
     * <p>
     * 签到需要加锁，避免用户连续点击签到多次的情况。[已完成]
     *
     * @param request
     * @return
     */
    @Override
    public IntegralSignInVO signIn(HttpServletRequest request) {
        IntegralSignInVO integralSignInVO = new IntegralSignInVO();
        User loginUser = userService.getLoginUser(request);
        Long userId = loginUser.getId();
        RLock lock = redisson.getLock(redisLockPrefix + loginUser.getId());
        try {
            lock.lock(); //加锁
            ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
            String nowDateLongStr = DateUtils.getBitmapDateKey(); //bitmap Key prefix
            String bitmapKey = nowDateLongStr + ":" + userId; //yyyy/dd/mm:userId

            //判断今天是否已经签到过
            Boolean aBoolean = valueOperations.getBit(bitmapKey, DateUtils.getNowDay() - 1);
            if (aBoolean) {
                //今天已经签到过
                integralSignInVO.setSignInBoolean(Boolean.FALSE);
                integralSignInVO.setMessage("今天已签到");
                log.info("IntegralServiceImpl signIn integralSignInVO:{}", JSONUtil.toJsonStr(integralSignInVO));
                return integralSignInVO;
            }

            //判断是否是连续签到 连续签到多少天 111111111111111111111111111111100011111011
            List<Long> longs = valueOperations.bitField(bitmapKey, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(Math.toIntExact(DateUtils.getNowDay()))).valueAt(0));
            Long aLong = longs.get(0);
            // todo 判断连续签到天数

            //保存redis
            valueOperations.setBit(bitmapKey, DateUtils.getNowDay(), Boolean.TRUE);

            //保存数据库
            final int integralAmount = 1; //暂时默认1分
            QueryWrapper<Integral> integralQueryWrapper = new QueryWrapper<>();
            integralQueryWrapper.eq("userId", userId);
            Integral integral = this.getOne(integralQueryWrapper);
            if (integral == null) {
                //第一次签到
                integral = new Integral();
                integral.setUserId(userId);
                integral.setIntegralAmount(integralAmount);
                integralSignInVO.setSignInBoolean(this.save(integral));
                integralSignInVO.setMessage("签到成功");
                log.info("IntegralServiceImpl signIn integralSignInVO: {}", JSONUtil.toJsonStr(integralSignInVO));
                return integralSignInVO;
            }
            integral.setUserId(userId);
            integral.setIntegralAmount(integral.getIntegralAmount() + integralAmount);
            integralSignInVO.setSignInBoolean(this.updateById(integral));
            integralSignInVO.setMessage("签到成功");
            log.info("IntegralServiceImpl signIn integralSignInVO: {}", JSONUtil.toJsonStr(integralSignInVO));
            return integralSignInVO;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    /**
     * 积分兑换接口调用次数
     *
     * @param integralExchangeInterfaceRequest
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public IntegralExchangeInterfaceVO interfaceExchangeByIntegral(IntegralExchangeInterfaceRequest integralExchangeInterfaceRequest, HttpServletRequest request) {
        /**
         * 操作流程
         * 先将积分扣减  再增加接口调用次数
         */
        if (integralExchangeInterfaceRequest == null) {
            log.info("IntegralServiceImpl interfaceExchangeByIntegral integralInterfaceRequest in null");
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        log.info("IntegralServiceImpl interfaceExchangeByIntegral integralInterfaceRequest:{}", JSONUtil.toJsonStr(integralExchangeInterfaceRequest));
        Long interfaceId = integralExchangeInterfaceRequest.getInterfaceId();
        if (interfaceId <= 0) {
            log.info("IntegralServiceImpl interfaceExchangeByIntegral interfaceId is null");
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "接口参数错误");
        }
        User loginUser = userService.getLoginUser(request);

        //兑换积分
        QueryWrapper<Integral> integralQueryWrapper = new QueryWrapper<>();
        integralQueryWrapper.eq("userId", loginUser.getId());
        Integral integral = this.getOne(integralQueryWrapper);
        if (integral == null) {
            log.info("IntegralServiceImpl interfaceExchangeByIntegral integral is null");
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户积分数据不存在");
        }
        //消耗多少积分 后续可以根据不同的接口兑换消耗的不同的积分数  暂时都消耗1积分
        int count = integralExchangeInterfaceRequest.getCount();
        int integralTotalAmount = count * integralAmountToOne; //此次消耗的总积分数

        //判断积分是否足够
        Integer userIntegralAmount = integral.getIntegralAmount();
        if (userIntegralAmount < integralTotalAmount) {
            log.info("IntegralServiceImpl interfaceExchangeByIntegral exchange err userIntegralAmount:{},integralTotalAmount:{}", userIntegralAmount, integralTotalAmount);
            return new IntegralExchangeInterfaceVO(Boolean.FALSE, "积分余额不足");
        }

        //判断当前兑换的接口是否存在
        QueryWrapper<UserInterfaceInfo> userInterfaceInfoQueryWrapper = new QueryWrapper<>();
        userInterfaceInfoQueryWrapper.eq("interfaceId", interfaceId);
        userInterfaceInfoQueryWrapper.eq("userId", loginUser.getId());
        UserInterfaceInfo userInterfaceInfo = userInterfaceInfoService.getOne(userInterfaceInfoQueryWrapper);
        if (userInterfaceInfo == null) {
            log.info("IntegralServiceImpl interfaceExchangeByIntegral userInterfaceInfo is null");
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "接口数据不存在");
        }

        //消耗积分
        integral.setIntegralAmount(userIntegralAmount - integralTotalAmount);
        this.updateById(integral);//报错会回滚

        //增加接口调用次数
        userInterfaceInfo.setLeftNum(userInterfaceInfo.getLeftNum() + count);
        userInterfaceInfo.setTotalNum(userInterfaceInfo.getTotalNum() + count);
        userInterfaceInfoService.updateById(userInterfaceInfo);

        //发送mq消息 创建积分订单
        MQIntegralModel mqIntegralModel = new MQIntegralModel();
        mqIntegralModel.setUserId(loginUser.getId());
        mqIntegralModel.setExpendIntegralAmount(integralTotalAmount);
        mqIntegralModel.setInterfaceId(userInterfaceInfo.getInterfaceInfoId());
        //mqIntegralModel.setExchangeProductName(); //获取接口的名称 可到mq中获取
        mqIntegralModel.setExchangeType(integralExchangeTypeMap.get("interface"));
        log.info("IntegralServiceImpl interfaceExchangeByIntegral mqIntegralModel:{}", JSONUtil.toJsonStr(mqIntegralModel));
        rocketMQTemplate.asyncSend(MQConstant.TOPIC_INTEGRAL_INTERFACE, mqIntegralModel, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("发送消息成功");
            }

            @Override
            public void onException(Throwable throwable) {
                log.info("发送消息失败,等待补偿操作");
            }
        });

        return new IntegralExchangeInterfaceVO(Boolean.TRUE, "兑换成功");
    }


    /**
     * 兑换永久刷新key服务
     *
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public IntegralExchangeRefreshKeyVO freshKeyExchangeByIntegral(HttpServletRequest request) {
        User loginUser = userService.getLoginUser(request);

        //兑换积分
        QueryWrapper<Integral> integralQueryWrapper = new QueryWrapper<>();
        integralQueryWrapper.eq("userId", loginUser.getId());
        Integral integral = this.getOne(integralQueryWrapper);
        if (integral == null) {
            log.info("IntegralServiceImpl freshKeyExchangeByIntegral integral is null");
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户积分数据不存在");
        }

        //判断积分是否足够
        Integer userIntegralAmount = integral.getIntegralAmount();
        if (userIntegralAmount < integralAmountToRefreshKey) {
            log.info("IntegralServiceImpl freshKeyExchangeByIntegral exchange err userIntegralAmount:{}", userIntegralAmount);
            return new IntegralExchangeRefreshKeyVO(Boolean.FALSE, "积分余额不足");
        }
        //兑换
        integral.setIntegralAmount(userIntegralAmount - integralAmountToRefreshKey);
        this.updateById(integral);//报错会回滚

        //更新用户表的vip字段
        loginUser.setIsVip(1);
        userService.updateById(loginUser);

        //发送mq消息
        MQIntegralModel mqIntegralModel = new MQIntegralModel();
        mqIntegralModel.setUserId(loginUser.getId());
        mqIntegralModel.setExpendIntegralAmount(integralAmountToRefreshKey);
        //mqIntegralModel.setExchangeProductName(); //获取接口的名称 可到mq中获取
        mqIntegralModel.setExchangeType(integralExchangeTypeMap.get("refreshKey"));
        log.info("IntegralServiceImpl freshKeyExchangeByIntegral mqIntegralModel:{}", JSONUtil.toJsonStr(mqIntegralModel));
        rocketMQTemplate.asyncSend(MQConstant.TOPIC_INTEGRAL_REFRESHKEY, mqIntegralModel, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("发送消息成功");
            }

            @Override
            public void onException(Throwable throwable) {
                log.info("发送消息失败,等待补偿操作");
            }
        });

        return new IntegralExchangeRefreshKeyVO(Boolean.TRUE, "兑换成功");
    }

    /**
     * 返回积分兑换的接口列表 是用户已经申请过的接口
     *
     * @param request
     * @return
     */
    @Override
    public List<IntegralVO> integralList(HttpServletRequest request) {
        User loginUser = userService.getLoginUser(request);
        log.info("IntegralServiceImpl integralList user:{}", JSONUtil.toJsonStr(loginUser));

        QueryWrapper<UserInterfaceInfo> userInterfaceInfoQueryWrapper = new QueryWrapper<>();
        userInterfaceInfoQueryWrapper.eq("userId", loginUser.getId());
        List<UserInterfaceInfo> userInterfaceInfoList = userInterfaceInfoService.list(userInterfaceInfoQueryWrapper);
        List<Long> interfaceIdList = userInterfaceInfoList.stream().map(UserInterfaceInfo::getInterfaceInfoId).collect(Collectors.toList());
        List<InterfaceInfo> interfaceInfos = interfaceInfoService.listByIds(interfaceIdList);

        //设置接口基本信息
        List<IntegralVO> integralVOList = interfaceInfos.stream().map(item -> {
            IntegralVO integralVO = new IntegralVO();
            integralVO.setInterfaceId(item.getId());
            integralVO.setInterfaceName(item.getName());
            integralVO.setInterfaceDesc(item.getDescription());
            integralVO.setStatus(item.getStatus());
            //设置调用接口次数所需要的积分
            integralVO.setIntegral(1); //暂时设置为1，后续根据需要再更改
            return integralVO;
        }).collect(Collectors.toList());

        //设置接口剩余调用次数
        integralVOList.stream().forEach(item -> {
            for (UserInterfaceInfo userInterfaceInfo : userInterfaceInfoList) {
                if (item.getInterfaceId().equals(userInterfaceInfo.getInterfaceInfoId())) {
                    item.setLeftNum(userInterfaceInfo.getLeftNum());
                }
            }
        });

        return integralVOList;
    }
}




